package utils

import (
	"errors"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"reflect"
)

type PaginationPageInfo struct {
	HasNextPage bool `json:"has_next_page"`
	HasPrevPage bool `json:"has_prev_page"`
	CurrentPage int  `json:"current_page"`
	CurrentSize int  `json:"current_size"`
}

type Pagination struct {
	Items      interface{}        `json:"items"`
	TotalCount int                `json:"total_count"`
	PageInfo   PaginationPageInfo `json:"page_info"`
}

type PaginationQuery struct {
	Page int `json:"page" form:"page" xml:"page" query:"page"`
	Size int `json:"size" form:"size" xml:"size" query:"page" binding:"required"`
}

func (q *PaginationQuery) IsInvalid() bool {
	return q.Size == 0
}
func GetPaginationFromContext(ctx *gin.Context) (res *PaginationQuery, err error) {
	res = &PaginationQuery{}
	if err = ctx.ShouldBindQuery(res); err != nil {
		if err = ctx.ShouldBind(res); err != nil {
			return
		}
	}
	err = nil
	return
}
func ToPagination(array interface{}, page int, size int) Pagination {
	valType := reflect.TypeOf(array)
	if !(valType.Kind() == reflect.Array || valType.Kind() == reflect.Slice) {
		panic(errors.New("need array or slice"))
	}
	val := reflect.ValueOf(array)
	cArray := make([]interface{}, val.Len())
	for i := 0; i < val.Len(); i++ {
		cArray[i] = val.Index(i)
	}
	arrayLen := len(cArray)
	hasNext := (page+1)*size < arrayLen
	hasPrev := page > 0
	beginIdx := page * size
	endIdx := (page + 1) * size

	if !hasNext {
		endIdx = arrayLen
	}

	return Pagination{
		Items:      cArray[beginIdx:endIdx],
		TotalCount: arrayLen,
		PageInfo: PaginationPageInfo{
			HasNextPage: hasNext,
			HasPrevPage: hasPrev,
			CurrentPage: page,
			CurrentSize: size,
		},
	}
}
func QueryAndPaginate(query *PaginationQuery, db *gorm.DB, model interface{}) (Pagination, error) {
	if query.Size == 0 {
		err := db.Find(model).Error
		return Pagination{
			Items: model,
		}, err
	}
	err := db.Limit(query.Size).Offset(query.Page * query.Size).Find(model).Error
	if err != nil {
		return Pagination{}, err
	}
	var total int64
	var modelItem interface{}
	ty := reflect.TypeOf(model)
	for kind := ty.Kind(); kind == reflect.Array || kind == reflect.Slice || kind == reflect.Ptr; {
		ty = ty.Elem()
		kind = ty.Kind()
	}
	modelItem = reflect.New(ty).Interface()
	delete(db.Statement.Clauses, clause.Limit{}.Name())
	db.Model(modelItem).Count(&total)
	return Pagination{
		Items:      model,
		TotalCount: int(total),
		PageInfo: PaginationPageInfo{
			HasPrevPage: query.Page > 0,
			HasNextPage: (query.Page+1)*query.Size < int(total),
			CurrentSize: query.Size,
			CurrentPage: query.Page,
		},
	}, nil
}
